Split the property editor code out for reuse, improve.
authorOwen Taylor <otaylor@src.gnome.org>
Thu, 3 May 2001 00:46:41 +0000 (00:46 +0000)
committerOwen Taylor <otaylor@src.gnome.org>
Thu, 3 May 2001 00:46:41 +0000 (00:46 +0000)
* tests/prop-editor.[ch] test/testtreeview.c tests/Makefile.am:
Split the property editor code out for reuse, improve.

tests/prop-editor.c [new file with mode: 0644]
tests/prop-editor.h [new file with mode: 0644]

diff --git a/tests/prop-editor.c b/tests/prop-editor.c
new file mode 100644 (file)
index 0000000..ac96abf
--- /dev/null
@@ -0,0 +1,586 @@
+/* prop-editor.c
+ * Copyright (C) 2000  Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <string.h>
+
+#include <gtk/gtk.h>
+
+#include "prop-editor.h"
+
+static void
+get_param_specs (GObject      *object,
+                 GParamSpec ***specs,
+                 gint         *n_specs)
+{
+  /* Use private interface for now, fix later */
+  *specs = G_OBJECT_GET_CLASS (object)->property_specs;
+  *n_specs = G_OBJECT_GET_CLASS (object)->n_property_specs;
+}
+
+typedef struct
+{
+  gpointer instance;
+  GObject *alive_object;
+  guint id;
+} DisconnectData;
+
+static void
+disconnect_func (gpointer data)
+{
+  DisconnectData *dd = data;
+  
+  g_signal_handler_disconnect (dd->instance, dd->id);
+}
+
+static void
+signal_removed (gpointer  data,
+               GClosure *closure)
+{
+  DisconnectData *dd = data;
+
+  g_object_steal_data (dd->alive_object, "alive-object-data");
+  g_free (dd);
+}
+
+static void
+g_object_connect_property (GObject *object,
+                           const gchar *prop_name,
+                           GtkSignalFunc func,
+                           gpointer data,
+                           GObject *alive_object)
+{
+  GClosure *closure;
+  gchar *with_detail = g_strconcat ("notify::", prop_name, NULL);
+  DisconnectData *dd;
+
+  dd = g_new (DisconnectData, 1);
+
+  closure = g_cclosure_new (func, data, NULL);
+
+  g_closure_add_invalidate_notifier (closure, dd, signal_removed);
+
+  dd->id = g_signal_connect_closure (object, with_detail,
+                                    closure, FALSE);
+
+  dd->instance = object;
+  dd->alive_object = alive_object;
+  
+  g_object_set_data_full (G_OBJECT (alive_object),
+                          "alive-object-data",
+                          dd,
+                          disconnect_func);
+  
+  g_free (with_detail);
+}
+
+typedef struct 
+{
+  GObject *obj;
+  gchar *prop;
+} ObjectProperty;
+
+static void
+free_object_property (ObjectProperty *p)
+{
+  g_free (p->prop);
+  g_free (p);
+}
+
+static void
+connect_controller (GObject *controller,
+                    const gchar *signal,
+                    GObject *model,
+                    const gchar *prop_name,
+                    GtkSignalFunc func)
+{
+  ObjectProperty *p;
+
+  p = g_new (ObjectProperty, 1);
+  p->obj = model;
+  p->prop = g_strdup (prop_name);
+
+  g_signal_connect_data (controller, signal, func, p,
+                         (GClosureNotify)free_object_property,
+                         FALSE, FALSE);
+}
+
+static void
+int_modified (GtkAdjustment *adj, gpointer data)
+{
+  ObjectProperty *p = data;
+
+  g_object_set (p->obj, p->prop, (int) adj->value, NULL);
+}
+
+static void
+int_changed (GObject *object, GParamSpec *pspec, gpointer data)
+{
+  GtkAdjustment *adj = GTK_ADJUSTMENT (data);
+  GValue val = { 0, };  
+
+  g_value_init (&val, G_TYPE_INT);
+  g_object_get_property (object, pspec->name, &val);
+
+  if (g_value_get_int (&val) != (int)adj->value)
+    gtk_adjustment_set_value (adj, g_value_get_int (&val));
+
+  g_value_unset (&val);
+}
+
+static void
+float_modified (GtkAdjustment *adj, gpointer data)
+{
+  ObjectProperty *p = data;
+
+  g_object_set (p->obj, p->prop, (float) adj->value, NULL);
+}
+
+static void
+float_changed (GObject *object, GParamSpec *pspec, gpointer data)
+{
+  GtkAdjustment *adj = GTK_ADJUSTMENT (data);
+  GValue val = { 0, };  
+
+  g_value_init (&val, G_TYPE_FLOAT);
+  g_object_get_property (object, pspec->name, &val);
+
+  if (g_value_get_float (&val) != (float) adj->value)
+    gtk_adjustment_set_value (adj, g_value_get_float (&val));
+
+  g_value_unset (&val);
+}
+
+static void
+string_modified (GtkEntry *entry, gpointer data)
+{
+  ObjectProperty *p = data;
+  const gchar *text;
+
+  text = gtk_entry_get_text (entry);
+
+  g_object_set (p->obj, p->prop, text, NULL);
+}
+
+static void
+string_changed (GObject *object, GParamSpec *pspec, gpointer data)
+{
+  GtkEntry *entry = GTK_ENTRY (data);
+  GValue val = { 0, };  
+  const gchar *str;
+  const gchar *text;
+  
+  g_value_init (&val, G_TYPE_STRING);
+  g_object_get_property (object, pspec->name, &val);
+
+  str = g_value_get_string (&val);
+  if (str == NULL)
+    str = "";
+  text = gtk_entry_get_text (entry);
+
+  if (strcmp (str, text) != 0)
+    gtk_entry_set_text (entry, str);
+  
+  g_value_unset (&val);
+}
+
+static void
+bool_modified (GtkToggleButton *tb, gpointer data)
+{
+  ObjectProperty *p = data;
+
+  g_object_set (p->obj, p->prop, (int) tb->active, NULL);
+}
+
+static void
+bool_changed (GObject *object, GParamSpec *pspec, gpointer data)
+{
+  GtkToggleButton *tb = GTK_TOGGLE_BUTTON (data);
+  GValue val = { 0, };  
+  
+  g_value_init (&val, G_TYPE_BOOLEAN);
+  g_object_get_property (object, pspec->name, &val);
+
+  if (g_value_get_boolean (&val) != tb->active)
+    gtk_toggle_button_set_active (tb, g_value_get_boolean (&val));
+
+  gtk_label_set_text (GTK_LABEL (GTK_BIN (tb)->child), g_value_get_boolean (&val) ?
+                      "TRUE" : "FALSE");
+  
+  g_value_unset (&val);
+}
+
+
+static void
+enum_modified (GtkOptionMenu *om, gpointer data)
+{
+  ObjectProperty *p = data;
+  gint i;
+  GParamSpec *spec;
+  GEnumClass *eclass;
+  
+  spec = g_object_class_find_property (G_OBJECT_GET_CLASS (p->obj),
+                                       p->prop);
+
+  eclass = G_ENUM_CLASS (g_type_class_peek (spec->value_type));
+  
+  i = gtk_option_menu_get_history (om);
+
+  g_object_set (p->obj, p->prop, eclass->values[i].value, NULL);
+}
+
+static void
+enum_changed (GObject *object, GParamSpec *pspec, gpointer data)
+{
+  GtkOptionMenu *om = GTK_OPTION_MENU (data);
+  GValue val = { 0, };  
+  GEnumClass *eclass;
+  gint i;
+
+  eclass = G_ENUM_CLASS (g_type_class_peek (pspec->value_type));
+  
+  g_value_init (&val, pspec->value_type);
+  g_object_get_property (object, pspec->name, &val);
+
+  i = 0;
+  while (i < eclass->n_values)
+    {
+      if (eclass->values[i].value == g_value_get_enum (&val))
+        break;
+      ++i;
+    }
+  
+  if (gtk_option_menu_get_history (om) != i)
+    gtk_option_menu_set_history (om, i);
+  
+  g_value_unset (&val);
+
+}
+
+static gunichar
+unichar_get_value (GtkEntry *entry)
+{
+  const gchar *text = gtk_entry_get_text (entry);
+  
+  if (text[0])
+    return g_utf8_get_char (text);
+  else
+    return 0;
+}
+
+static void
+unichar_modified (GtkEntry *entry, gpointer data)
+{
+  ObjectProperty *p = data;
+  gunichar val = unichar_get_value (entry);
+
+  g_object_set (p->obj, p->prop, val, NULL);
+}
+
+static void
+unichar_changed (GObject *object, GParamSpec *pspec, gpointer data)
+{
+  GtkEntry *entry = GTK_ENTRY (data);
+  gunichar new_val;
+  gunichar old_val = unichar_get_value (entry);
+  gchar buf[7];
+  gint len;
+
+  g_object_get (object, pspec->name, &new_val, NULL);
+
+  if (new_val != old_val)
+    {
+      if (!new_val)
+       len = 0;
+      else
+       len = g_unichar_to_utf8 (new_val, buf);
+      
+      buf[len] = '\0';
+      
+      gtk_entry_set_text (entry, buf);
+    }
+}
+
+void
+model_destroy (gpointer data)
+{
+  g_object_steal_data (data, "model-object");
+  gtk_widget_destroy (data);
+}
+
+void
+window_destroy (gpointer data)
+{
+  g_object_steal_data (data, "prop-editor-win");
+}
+
+GtkWidget*
+create_prop_editor (GObject *object)
+{
+  GtkWidget *win;
+  GtkWidget *vbox;
+  GtkWidget *hbox;
+  GtkWidget *label;
+  GtkWidget *prop_edit;
+  GtkWidget *sw;
+  gint n_specs = 0;
+  GParamSpec **specs = NULL;
+  gint i;
+  GtkAdjustment *adj;
+  GtkTooltips *tips;
+
+  win = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+
+  tips = gtk_tooltips_new ();
+  gtk_signal_connect_object (GTK_OBJECT (win), "destroy",
+                            GTK_SIGNAL_FUNC (gtk_object_destroy), GTK_OBJECT (tips));
+
+  /* hold a weak ref to the object we're editing */
+  g_object_set_data_full (G_OBJECT (object), "prop-editor-win", win, model_destroy);
+  g_object_set_data_full (G_OBJECT (win), "model-object", object, window_destroy);
+  
+  vbox = gtk_vbox_new (TRUE, 2);
+
+  sw = gtk_scrolled_window_new (NULL, NULL);
+  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
+                                  GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+  
+  gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (sw), vbox);
+  gtk_container_add (GTK_CONTAINER (win), sw);
+  
+  get_param_specs (object, &specs, &n_specs);
+  
+  i = 0;
+  while (i < n_specs)
+    {
+      GParamSpec *spec = specs[i];
+      gboolean can_modify;
+      
+      prop_edit = NULL;
+
+      can_modify = ((spec->flags & G_PARAM_WRITABLE) != 0 &&
+                    (spec->flags & G_PARAM_CONSTRUCT_ONLY) == 0);
+      
+      if ((spec->flags & G_PARAM_READABLE) == 0)
+        {
+          /* can't display unreadable properties */
+          ++i;
+          continue;
+        }
+      
+      switch (G_PARAM_SPEC_TYPE (spec))
+        {
+        case G_TYPE_PARAM_INT:
+          hbox = gtk_hbox_new (FALSE, 10);
+          label = gtk_label_new (spec->nick);
+          gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+          gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
+          adj = GTK_ADJUSTMENT (gtk_adjustment_new (G_PARAM_SPEC_INT (spec)->default_value,
+                                                    G_PARAM_SPEC_INT (spec)->minimum,
+                                                    G_PARAM_SPEC_INT (spec)->maximum,
+                                                    1,
+                                                    MAX ((G_PARAM_SPEC_INT (spec)->maximum -
+                                                          G_PARAM_SPEC_INT (spec)->minimum) / 10, 1),
+                                                    0.0));
+
+          prop_edit = gtk_spin_button_new (adj, 1.0, 0);
+          gtk_box_pack_end (GTK_BOX (hbox), prop_edit, FALSE, FALSE, 0);
+          
+          gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); 
+
+          g_object_connect_property (object, spec->name,
+                                     GTK_SIGNAL_FUNC (int_changed),
+                                     adj, G_OBJECT (adj));
+
+          if (can_modify)
+            connect_controller (G_OBJECT (adj), "value_changed",
+                                object, spec->name, (GtkSignalFunc) int_modified);
+          break;
+
+        case G_TYPE_PARAM_FLOAT:
+          hbox = gtk_hbox_new (FALSE, 10);
+          label = gtk_label_new (spec->nick);
+          gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+          gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
+          adj = GTK_ADJUSTMENT (gtk_adjustment_new (G_PARAM_SPEC_FLOAT (spec)->default_value,
+                                                    G_PARAM_SPEC_FLOAT (spec)->minimum,
+                                                    G_PARAM_SPEC_FLOAT (spec)->maximum,
+                                                    0.1,
+                                                    MAX ((G_PARAM_SPEC_FLOAT (spec)->maximum -
+                                                          G_PARAM_SPEC_FLOAT (spec)->minimum) / 10, 0.1),
+                                                    0.0));
+
+          prop_edit = gtk_spin_button_new (adj, 0.1, 2);
+          
+          gtk_box_pack_end (GTK_BOX (hbox), prop_edit, FALSE, FALSE, 0);
+          
+          gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); 
+
+          g_object_connect_property (object, spec->name,
+                                     GTK_SIGNAL_FUNC (float_changed),
+                                     adj, G_OBJECT (adj));
+
+          if (can_modify)
+            connect_controller (G_OBJECT (adj), "value_changed",
+                                object, spec->name, (GtkSignalFunc) float_modified);
+          break;
+          
+        case G_TYPE_PARAM_STRING:
+          hbox = gtk_hbox_new (FALSE, 10);
+          label = gtk_label_new (spec->nick);
+          gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+          gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
+
+          prop_edit = gtk_entry_new ();
+          gtk_box_pack_end (GTK_BOX (hbox), prop_edit, FALSE, FALSE, 0);
+          
+          gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); 
+
+          g_object_connect_property (object, spec->name,
+                                     GTK_SIGNAL_FUNC (string_changed),
+                                     prop_edit, G_OBJECT (prop_edit));
+
+          if (can_modify)
+            connect_controller (G_OBJECT (prop_edit), "changed",
+                                object, spec->name, (GtkSignalFunc) string_modified);
+          break;
+
+        case G_TYPE_PARAM_BOOLEAN:
+          hbox = gtk_hbox_new (FALSE, 10);
+          label = gtk_label_new (spec->nick);
+          gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+          gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
+
+          prop_edit = gtk_toggle_button_new_with_label ("");
+          gtk_box_pack_end (GTK_BOX (hbox), prop_edit, FALSE, FALSE, 0);
+          
+          gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); 
+
+          g_object_connect_property (object, spec->name,
+                                     GTK_SIGNAL_FUNC (bool_changed),
+                                     prop_edit, G_OBJECT (prop_edit));
+
+          if (can_modify)
+            connect_controller (G_OBJECT (prop_edit), "toggled",
+                                object, spec->name, (GtkSignalFunc) bool_modified);
+          break;
+          
+        case G_TYPE_PARAM_ENUM:
+         {
+           GtkWidget *menu;
+           GEnumClass *eclass;
+           gint i;
+            
+           hbox = gtk_hbox_new (FALSE, 10);
+           label = gtk_label_new (spec->nick);
+           gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+           gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
+            
+           prop_edit = gtk_option_menu_new ();
+           
+           menu = gtk_menu_new ();
+
+           eclass = G_ENUM_CLASS (g_type_class_ref (spec->value_type));
+           
+           i = 0;
+           while (i < eclass->n_values)
+             {
+               GtkWidget *mi;
+                
+               mi = gtk_menu_item_new_with_label (eclass->values[i].value_name);
+               
+               gtk_widget_show (mi);
+                
+               gtk_menu_shell_append (GTK_MENU_SHELL (menu), mi);
+                
+                  ++i;
+             }
+           
+           g_type_class_unref (eclass);
+           
+           gtk_option_menu_set_menu (GTK_OPTION_MENU (prop_edit), menu);
+           
+           gtk_box_pack_end (GTK_BOX (hbox), prop_edit, FALSE, FALSE, 0);
+            
+           gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); 
+            
+           g_object_connect_property (object, spec->name,
+                                      GTK_SIGNAL_FUNC (enum_changed),
+                                      prop_edit, G_OBJECT (prop_edit));
+            
+           if (can_modify)
+             connect_controller (G_OBJECT (prop_edit), "changed",
+                                 object, spec->name, (GtkSignalFunc) enum_modified);
+         }
+         
+        case G_TYPE_PARAM_UNICHAR:
+          hbox = gtk_hbox_new (FALSE, 10);
+          label = gtk_label_new (spec->nick);
+          gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+          gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
+
+         prop_edit = gtk_entry_new ();
+         gtk_entry_set_max_length (GTK_ENTRY (prop_edit), 1);
+          gtk_box_pack_end (GTK_BOX (hbox), prop_edit, FALSE, FALSE, 0);
+          
+          gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); 
+
+          g_object_connect_property (object, spec->name,
+                                     GTK_SIGNAL_FUNC (unichar_changed),
+                                     prop_edit, G_OBJECT (prop_edit));
+
+          if (can_modify)
+            connect_controller (G_OBJECT (prop_edit), "changed",
+                                object, spec->name, (GtkSignalFunc) unichar_modified);
+          break;
+
+       default:
+         {
+           gchar *msg = g_strdup_printf ("%s: don't know how to edit property type %s",
+                                         spec->nick, g_type_name (G_PARAM_SPEC_TYPE (spec)));
+           hbox = gtk_hbox_new (FALSE, 10);
+           label = gtk_label_new (msg);            
+           g_free (msg);
+           gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+           gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
+           gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
+         }
+        }
+
+      if (prop_edit)
+        {
+          if (!can_modify)
+            gtk_widget_set_sensitive (prop_edit, FALSE);
+
+         if (spec->blurb)
+           gtk_tooltips_set_tip (tips, prop_edit, spec->blurb, NULL);
+         
+          /* set initial value */
+          g_object_notify (object, spec->name);
+        }
+      
+      ++i;
+    }
+
+  gtk_window_set_default_size (GTK_WINDOW (win), 300, 500);
+  
+  gtk_widget_show_all (win);
+
+  return win;
+}
+
diff --git a/tests/prop-editor.h b/tests/prop-editor.h
new file mode 100644 (file)
index 0000000..e9d4411
--- /dev/null
@@ -0,0 +1,30 @@
+/* prop-editor.h
+ * Copyright (C) 2000  Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+#include <gtk/gtkwidget.h>
+
+#ifndef __PROP_EDITOR_H__
+#define __PROP_EDITOR_H__
+
+G_BEGIN_DECLS
+
+GtkWidget *create_prop_editor (GObject *object);
+
+G_END_DECLS
+
+#endif /* __PROP_EDITOR_H__ */